package grpc

import (
	"context"
	"encoding/json"
	"strconv"
	"sync"
	"time"

	"go-toolkit/library/conf/env"
	"go-toolkit/library/log"
	bm "go-toolkit/library/net/http/blademaster"
	"go-toolkit/library/net/http/blademaster/binding"
	"go-toolkit/library/net/metadata"
	"go-toolkit/library/net/netutil"
	"go-toolkit/library/net/rpc/warden"
	xtime "go-toolkit/library/time"

	"github.com/gogo/protobuf/proto"
	"github.com/pkg/errors"
	"google.golang.org/grpc"
)

// AppID unique app id for service discovery
const AppID = "datacenter.lancer.gateway2-server"
const _maxRetry = 10

var _eventMeta = map[string]string{
	"version": "2.0",
}

func defaultLogID() string {
	switch env.DeployEnv {
	case "uat":
		return "000356"
	default:
		return "001940"
	}
}

func defaultChanSize() int64 {
	return 2048
}

func defaultWarden() *warden.ClientConfig {
	switch env.DeployEnv {
	case "prod":
		return &warden.ClientConfig{
			Dial:    xtime.Duration(time.Second * 10),
			Timeout: xtime.Duration(time.Millisecond * 50),
			Subset:  50,
		}
	default:
		return &warden.ClientConfig{
			Dial:    xtime.Duration(time.Second * 10),
			Timeout: xtime.Duration(time.Millisecond * 250),
			Subset:  50,
		}
	}
}

// Config is
type Config struct {
	EventID string

	Warden   *warden.ClientConfig
	LogID    string
	ChanSize int64
}

// Infoc is
type Infoc struct {
	Gateway2ServerClient

	conf   *Config
	events chan *SimpleEvent
	pool   sync.Pool
	waiter sync.WaitGroup
}

// NewClient new member grpc client
func NewClient(cfg *warden.ClientConfig, opts ...grpc.DialOption) (Gateway2ServerClient, error) {
	client := warden.NewClient(cfg, opts...)
	conn, err := client.Dial(context.Background(), "discovery://default/"+AppID)
	if err != nil {
		return nil, err
	}
	return NewGateway2ServerClient(conn), nil
}

// New is
func New(conf *Config) *Infoc {
	if conf == nil {
		panic("Invalid gRPC infoc config")
	}
	if conf.EventID == "" {
		panic("Empty infoc EventID")
	}

	if conf.LogID == "" {
		conf.LogID = defaultLogID()
	}
	if conf.ChanSize == 0 {
		conf.ChanSize = defaultChanSize()
	}
	if conf.Warden == nil {
		conf.Warden = defaultWarden()
	}

	infoc := &Infoc{
		conf:   conf,
		events: make(chan *SimpleEvent, conf.ChanSize),
		pool: sync.Pool{
			New: func() interface{} {
				return &SimpleEvent{}
			},
		},
	}
	client, err := NewClient(conf.Warden)
	if err != nil {
		panic(err)
	}
	infoc.Gateway2ServerClient = client
	infoc.waiter.Add(1)
	go infoc.writeproc()
	return infoc
}

// Info is
func (i *Infoc) Info(in *ServerInfo) error {
	event := i.pool.Get().(*SimpleEvent)
	event.LogId = i.conf.LogID
	event.Meta = _eventMeta

	in.EventId = i.conf.EventID
	data, err := proto.Marshal(in)
	if err != nil {
		return err
	}
	event.Data = data
	select {
	case i.events <- event:
	default:
		i.freeEvent(event)
		return errors.New("infoc: chan buffer full")
	}
	return nil
}

// Infov is
func (i *Infoc) Infov(ctx context.Context, in *ServerInfo) error {
	if metadata.Bool(ctx, metadata.Mirror) {
		return nil
	}
	return i.Info(in)
}

// writeproc write data into connection.
func (i *Infoc) writeproc() {
	bc := netutil.BackoffConfig{
		MaxDelay:  15 * time.Second,
		BaseDelay: 1.0 * time.Second,
		Factor:    1.6,
		Jitter:    0.2,
	}
	for {
		event, ok := <-i.events
		if !ok {
			break
		}
		j := 0
		for j = 0; j < _maxRetry; j++ {
			reply, err := i.Send(context.Background(), event)
			if err != nil {
				log.Error("Failed to send event: %+v: %+v", event, err)
				time.Sleep(bc.Backoff(j))
				continue
			}
			if StatusCode(reply.Code) != StatusCode_SUCCESS {
				log.Warn("Failed to send event: %+v", reply)
				time.Sleep(bc.Backoff(j))
				continue
			}
			break
		}
		if j >= _maxRetry {
			log.Error("infoc reached max retry times")
		}
		i.freeEvent(event)
	}
	i.waiter.Done()
}

func (i *Infoc) freeEvent(event *SimpleEvent) {
	event.Reset()
	i.pool.Put(event)
}

// Polaris is
func Polaris(ctx *bm.Context) (*ServerInfo, error) {
	//d, _ := ctx.Get("device")
	//device, ok := d.(*bm.Device)
	//if !ok {
	//	return nil, errors.New("No device middleware")
	//}
	m, _ := ctx.Get("mid")
	mid, _ := m.(int64)

	param := struct {
		Ts         int64  `form:"ts"`
		Statistics string `form:"statistics"`
	}{}
	if err := binding.Form.Bind(ctx.Request, &param); err != nil {
		log.Warn("Failed to parse request: %+v", err)
	}
	statistics := struct {
		AppID    int32  `json:"appId"`
		Platform int32  `json:"platform"`
		Version  string `json:"version"`
		ABTest   string `json:"abtest"`
	}{}
	if param.Statistics != "" {
		if err := json.Unmarshal([]byte(param.Statistics), &statistics); err != nil {
			log.Warn("Failed to parse request statistics: %s: %+v", param.Statistics, err)
		}
	}
	s := &ServerInfo{
		RequestUrl:     ctx.Request.URL.String(),
		TimeIso:        time.Now().UnixNano() / 1e6,
		Ip:             metadata.String(ctx, metadata.RemoteIP),
		AppId:          statistics.AppID,
		Platform:       statistics.Platform,
		//Buvid:          device.Buvid,
		Abtest:         statistics.ABTest,
		Version:        statistics.Version,
		//VersionCode:    strconv.FormatInt(device.Build, 10),
		Mid:            strconv.FormatInt(mid, 10),
		Ctime:          param.Ts * 1000,
		ExtendedFields: map[string]string{},
	}
	return s, nil
}

// Close close the connection.
func (i *Infoc) Close() error {
	close(i.events)
	i.waiter.Wait()
	return nil
}
